<template>
  <section id="node-section" class="node-section container hero">
    <b-sidebar
      v-show="connectedServerInfo.length"
      :open="true"
      class="node-status-sidebar-reduced"
      :can-cancel="false"
      @mouseenter.native="showSidebar = true"
      @click.native="showSidebar = true"
    >
      <img src="@/assets/img/switch-menu.svg" width="36px" />
    </b-sidebar>
    <b-sidebar
      :open="showSidebar"
      type="is-light"
      :fullheight="false"
      :fullwidth="false"
      :overlay="false"
      :right="false"
      class="node-status-sidebar"
      :can-cancel="['outside']"
      @close="showSidebar = false"
      @mouseleave.native="showSidebar = false"
    >
      <b-message
        v-for="v of connectedServerInfo"
        :key="v.value"
        :title="`${v.info.name}${
          v.info.subscription_name ? ` [${v.info.subscription_name}]` : ''
        }`"
        :closable="false"
        size="is-small"
        :type="
          v.info.alive
            ? v.selected
              ? 'is-primary'
              : 'is-success'
            : v.info.alive === null
            ? 'is-light'
            : 'is-danger'
        "
        @click.native="handleClickConnectedServer(v.which)"
      >
        <div v-if="v.showContent">
          <p>{{ $t("server.protocol") }}: {{ v.info.net }}</p>
          <p v-if="v.info.delay && v.info.delay < 99999">
            {{ $t("server.latency") }}: {{ v.info.delay }}ms
          </p>
          <p v-if="!v.info.alive && v.info.last_seen_time">
            {{ $t("server.lastSeenTime") }}:
            {{ v.info.last_seen_time | unix2datetime }}
          </p>
          <p v-if="v.info.last_try_time">
            {{ $t("server.lastTryTime") }}:
            {{ v.info.last_try_time | unix2datetime }}
          </p>
        </div>
      </b-message>
    </b-sidebar>
    <div v-if="ready" class="hero-body">
      <b-field
        id="toolbar"
        grouped
        group-multiline
        :style="{
          background: overHeight
            ? isCheckedRowsPingable() || isCheckedRowsDeletable()
              ? 'rgba(0, 0, 0, 0.1)'
              : 'rgba(0, 0, 0, 0.05)'
            : 'transparent',
        }"
        :class="{ 'float-toolbar': overHeight }"
      >
        <div style="max-width: 60%">
          <button
            :class="{
              button: true,
              field: true,
              'is-info': true,
              'mobile-small': true,
              'not-display': !overHeight && !isCheckedRowsPingable(),
            }"
            :disabled="!isCheckedRowsPingable()"
            @click="handleClickLatency(true)"
          >
            <i class="iconfont icon-wave" />
            <span>PING</span>
          </button>
          <button
            :class="{
              button: true,
              field: true,
              'is-info': true,
              'mobile-small': true,
              'not-display': !overHeight && !isCheckedRowsPingable(),
            }"
            :disabled="!isCheckedRowsPingable()"
            @click="handleClickLatency(false)"
          >
            <i class="iconfont icon-wave" />
            <span>HTTP</span>
          </button>
          <button
            :class="{
              button: true,
              field: true,
              'is-delete': true,
              'mobile-small': true,
              'not-display': !overHeight && !isCheckedRowsDeletable(),
            }"
            :disabled="!isCheckedRowsDeletable()"
            @click="handleClickDelete"
          >
            <i class="iconfont icon-delete" />
            <span>{{ $t("operations.delete") }}</span>
          </button>
          <button
            :class="{
              button: true,
              field: true,
              'is-delete': true,
              'mobile-small': true,
              'not-show': true,
            }"
          >
            <i class="iconfont icon-delete" />
            <span>placeholder</span>
          </button>
          <span class="field not-show mobile-small">placeholder</span>
        </div>
        <div class="right">
          <b-button
            class="field mobile-small"
            type="is-primary"
            @click="handleClickCreate"
          >
            <i class="iconfont icon-chuangjiangongdan1" />
            <span>{{ $t("operations.create") }}</span>
          </b-button>
          <b-button
            class="field mobile-small"
            type="is-primary"
            @click="handleClickImport"
          >
            <i class="iconfont icon-daoruzupu-xianxing" />
            <span>{{ $t("operations.import") }}</span>
          </b-button>
        </div>
      </b-field>

      <b-collapse
        v-if="!tableData.subscriptions.length && !tableData.servers.length"
        class="card welcome-driver"
        aria-id="contentIdForA11y3"
      >
        <div
          slot="trigger"
          slot-scope="props"
          class="card-header"
          role="button"
          aria-controls="contentIdForA11y3"
        >
          <p class="card-header-title">
            {{ $t("welcome.title") }}
          </p>
          <a class="card-header-icon">
            <b-icon :icon="props.open ? 'menu-down' : 'menu-up'"></b-icon>
          </a>
        </div>
        <div class="card-content">
          <div class="content">
            <p>{{ $t("welcome.messages.0") }}</p>
            <p>{{ $t("welcome.messages.1") }}</p>
          </div>
        </div>
        <footer class="card-footer">
          <a class="card-footer-item" @click="handleClickCreate">{{
            $t("operations.create")
          }}</a>
          <a class="card-footer-item" @click="handleClickImport">{{
            $t("operations.import")
          }}</a>
        </footer>
      </b-collapse>
      <b-tabs
        v-if="tableData.subscriptions.length || tableData.servers.length"
        v-model="tab"
        position="is-centered"
        type="is-toggle-rounded"
        class="main-tabs"
        @input="handleTabsChange"
      >
        <b-tab-item label="SUBSCRIPTION">
          <b-field :label="`SUBSCRIPTION(${tableData.subscriptions.length})`">
            <b-table
              :data="tableData.subscriptions"
              :checked-rows.sync="checkedRows"
              :row-class="
                (row, index) =>
                  row.connected &&
                  runningState.running === $t('common.isRunning')
                    ? 'is-connected-running'
                    : row.connected
                    ? 'is-connected-not-running'
                    : null
              "
              default-sort="id"
              checkable
            >
              <b-table-column
                v-slot="props"
                field="id"
                label="ID"
                numeric
                sortable
              >
                {{ props.row.id }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="host"
                :label="$t('subscription.host')"
                sortable
              >
                {{ props.row.host }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="remarks"
                :label="$t('subscription.remarks')"
                sortable
              >
                {{ props.row.remarks }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="status"
                :label="$t('subscription.timeLastUpdate')"
                width="260"
                sortable
              >
                {{ props.row.status }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                :label="$t('subscription.numberServers')"
                centered
                numeric
                sortable
                :custom-sort="sortNumberServers"
              >
                {{ props.row.servers.length }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                :label="$t('operations.name')"
                width="300"
              >
                <div class="operate-box">
                  <b-button
                    size="is-small"
                    icon-left=" github-circle iconfont icon-sync"
                    outlined
                    type="is-warning"
                    @click="handleClickUpdateSubscription(props.row)"
                  >
                    {{ $t("operations.update") }}
                  </b-button>
                  <b-button
                    size="is-small"
                    icon-left=" github-circle iconfont icon-wendangxiugai"
                    outlined
                    type="is-info"
                    @click="handleClickModifySubscription(props.row)"
                  >
                    {{ $t("operations.modify") }}
                  </b-button>
                  <b-button
                    size="is-small"
                    icon-left=" github-circle iconfont icon-share"
                    outlined
                    type="is-success"
                    @click="handleClickShare(props.row)"
                  >
                    {{ $t("operations.share") }}
                  </b-button>
                </div>
              </b-table-column>
            </b-table>
          </b-field>
        </b-tab-item>
        <b-tab-item
          label="SERVER"
          :icon="`${
            connectedServerInTab['server'] ? ' iconfont icon-dian' : ''
          }`"
        >
          <b-field :label="`SERVER(${tableData.servers.length})`">
            <b-table
              per-page="100"
              :current-page.sync="currentPage.servers"
              :data="tableData.servers"
              :checked-rows.sync="checkedRows"
              checkable
              :row-class="
                (row, index) =>
                  row.connected &&
                  runningState.running === $t('common.isRunning')
                    ? 'is-connected-running'
                    : row.connected
                    ? 'is-connected-not-running'
                    : null
              "
              default-sort="id"
            >
              <b-table-column
                v-slot="props"
                field="id"
                label="ID"
                numeric
                sortable
              >
                {{ props.row.id }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="name"
                :label="$t('server.name')"
                sortable
              >
                {{ props.row.name }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="address"
                :label="$t('server.address')"
                sortable
              >
                <p 
                  class="address-column" 
                  :title="props.row.address">
                  {{ props.row.address }}
                </p>
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="net"
                :label="$t('server.protocol')"
                sortable
              >
                {{ props.row.net }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="pingLatency"
                :label="$t('server.latency')"
                class="ping-latency"
                sortable
                :custom-sort="sortping"
              >
                <p 
                  :class="{
                      'latency-column': true,
                      'latency-valid': props.row.pingLatency.endsWith('ms')
                    }" 
                  :title="props.row.pingLatency">
                  {{ props.row.pingLatency }}
                </p>
              </b-table-column>
              <b-table-column
                v-slot="props"
                :label="$t('operations.name')"
                sortable
                :custom-sort="sortConnections"
                width="300"
              >
                <div class="operate-box">
                  <b-button
                    size="is-small"
                    :icon-left="` github-circle iconfont ${
                      props.row.connected
                        ? 'icon-Link_disconnect'
                        : 'icon-lianjie'
                    }`"
                    :outlined="!props.row.connected"
                    :type="props.row.connected ? 'is-warning' : 'is-warning'"
                    @click="handleClickAboutConnection(props.row)"
                  >
                    {{
                      loadBalanceValid
                        ? props.row.connected
                          ? $t("operations.cancel")
                          : $t("operations.select")
                        : props.row.connected
                        ? $t("operations.disconnect")
                        : $t("operations.connect")
                    }}
                  </b-button>
                  <b-button
                    size="is-small"
                    icon-left=" github-circle iconfont icon-wendangxiugai"
                    :outlined="!props.row.connected"
                    type="is-info"
                    @click="handleClickModifyServer(props.row)"
                  >
                    {{ $t("operations.modify") }}
                  </b-button>
                  <b-button
                    size="is-small"
                    icon-left=" github-circle iconfont icon-share"
                    :outlined="!props.row.connected"
                    type="is-success"
                    @click="handleClickShare(props.row)"
                  >
                    {{ $t("operations.share") }}
                  </b-button>
                </div>
              </b-table-column>
            </b-table>
          </b-field>
        </b-tab-item>
        <b-tab-item
          v-for="(sub, subi) of tableData.subscriptions"
          :key="sub.id"
          :label="
            (sub.remarks && sub.remarks.toUpperCase()) || sub.host.toUpperCase()
          "
          :icon="`${
            connectedServerInTab['subscriptionServer'][subi]
              ? ' iconfont icon-dian'
              : ''
          }`"
        >
          <b-field
            v-if="tab === subi + 2"
            :label="`${sub.host.toUpperCase()}(${sub.servers.length}${
              sub.info ? ') (' : ''
            }${sub.info})`"
          >
            <b-table
              :current-page.sync="currentPage[sub.id]"
              per-page="100"
              :data="sub.servers"
              :checked-rows.sync="checkedRows"
              checkable
              :row-class="
                (row, index) =>
                  row.connected &&
                  runningState.running === $t('common.isRunning')
                    ? 'is-connected-running'
                    : row.connected
                    ? 'is-connected-not-running'
                    : null
              "
              default-sort="id"
            >
              <b-table-column
                v-slot="props"
                field="id"
                label="ID"
                numeric
                sortable
              >
                {{ props.row.id }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="name"
                :label="$t('server.name')"
                sortable
              >
                {{ props.row.name }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="address"
                :label="$t('server.address')"
                sortable
              >
                <p 
                  class="address-column" 
                  :title="props.row.address">
                  {{ props.row.address }}
                </p>
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="net"
                :label="$t('server.protocol')"
                style="font-size: 0.9em"
                sortable
              >
                {{ props.row.net }}
              </b-table-column>
              <b-table-column
                v-slot="props"
                field="pingLatency"
                :label="$t('server.latency')"
                class="ping-latency"
                sortable
                :custom-sort="sortping"
              >
                <p 
                   :class="{
                      'latency-column': true,
                      'latency-valid': props.row.pingLatency.endsWith('ms')
                    }" 
                  :title="props.row.pingLatency">
                  {{ props.row.pingLatency }}
                </p>
              </b-table-column>
              <b-table-column
                v-slot="props"
                :label="$t('operations.name')"
                sortable
                :custom-sort="sortConnections"
                width="300"
              >
                <div class="operate-box">
                  <b-button
                    size="is-small"
                    :icon-left="` github-circle iconfont ${
                      props.row.connected
                        ? 'icon-Link_disconnect'
                        : 'icon-lianjie'
                    }`"
                    :outlined="!props.row.connected"
                    :type="props.row.connected ? 'is-warning' : 'is-warning'"
                    @click="handleClickAboutConnection(props.row, subi)"
                  >
                    {{
                      loadBalanceValid
                        ? props.row.connected
                          ? $t("operations.cancel")
                          : $t("operations.select")
                        : props.row.connected
                        ? $t("operations.disconnect")
                        : $t("operations.connect")
                    }}
                  </b-button>
                  <b-button
                    size="is-small"
                    icon-left=" github-circle iconfont icon-winfo-icon-chakanbaogao"
                    :outlined="!props.row.connected"
                    type="is-info"
                    @click="handleClickViewServer(props.row, subi)"
                  >
                    {{ $t("operations.view") }}
                  </b-button>
                  <b-button
                    size="is-small"
                    icon-left=" github-circle iconfont icon-share"
                    :outlined="!props.row.connected"
                    type="is-success"
                    @click="handleClickShare(props.row, subi)"
                  >
                    {{ $t("operations.share") }}
                  </b-button>
                </div>
              </b-table-column>
            </b-table>
          </b-field>
        </b-tab-item>
      </b-tabs>
    </div>
    <b-loading v-else :is-full-page="true" :active="true">
      <i class="iconfont icon-loading_ico-copy" />
    </b-loading>
    <b-modal
      :active.sync="showModalServer"
      has-modal-card
      trap-focus
      aria-role="dialog"
      aria-modal
    >
      <ModalServer
        :which="which"
        :readonly="modalServerReadOnly"
        @submit="handleModalServerSubmit"
      />
    </b-modal>
    <b-modal
      :active.sync="showModalSubscription"
      has-modal-card
      trap-focus
      aria-role="dialog"
      aria-modal
    >
      <ModalSubscription
        :which="which"
        @submit="handleModalSubscriptionSubmit"
      />
    </b-modal>
    <input
      id="QRCodeImport"
      type="file"
      style="display: none"
      accept="image/*"
    />
    <b-modal
      :active.sync="showModalImport"
      has-modal-card
      trap-focus
      aria-role="dialog"
      aria-modal
      @after-enter="handleModalImportShow"
    >
      <div class="modal-card" style="width: 350px">
        <header class="modal-card-head">
          <p class="modal-card-title">{{ $t("operations.import") }}</p>
        </header>
        <section class="modal-card-body">
          {{ $t("import.message") }}
          <b-input
            ref="importInput"
            v-model="importWhat"
            icon-right=" iconfont icon-camera"
            icon-right-clickable
            @icon-right-click="handleClickImportQRCode"
            @keyup.native="handleImportEnter"
          ></b-input>
        </section>
        <footer class="modal-card-foot">
          <button
            class="button is-link is-light"
            type="button"
            @click="handleClickImportInBatch"
          >
            {{ $t("operations.inBatch") }}
          </button>
          <div
            style="
              display: flex;
              justify-content: flex-end;
              width: -moz-available;
            "
          >
            <button
              class="button"
              type="button"
              @click="showModalImport = false"
            >
              {{ $t("operations.cancel") }}
            </button>
            <button
              class="button is-primary"
              type="button"
              @click="handleClickImportConfirm"
            >
              {{ $t("operations.confirm") }}
            </button>
          </div>
        </footer>
      </div>
    </b-modal>
    <b-modal
      :active.sync="showModalImportInBatch"
      has-modal-card
      trap-focus
      aria-role="dialog"
      aria-modal
      @close="showModalImport = false"
    >
      <div class="modal-card" style="width: 350px">
        <header class="modal-card-head">
          <p class="modal-card-title">{{ $t("operations.import") }}</p>
        </header>
        <section class="modal-card-body">
          {{ $t("import.batchMessage") }}
          <b-input
            ref="importInput"
            v-model="importWhat"
            type="textarea"
            custom-class="horizon-scroll"
          ></b-input>
        </section>
        <footer class="modal-card-foot">
          <div
            style="
              display: flex;
              justify-content: flex-end;
              width: -moz-available;
            "
          >
            <button
              class="button"
              type="button"
              @click="
                () => {
                  showModalImport = false;
                  showModalImportInBatch = false;
                }
              "
            >
              {{ $t("operations.cancel") }}
            </button>
            <button
              class="button is-primary"
              type="button"
              @click="handleClickImportConfirm"
            >
              {{ $t("operations.confirm") }}
            </button>
          </div>
        </footer>
      </div>
    </b-modal>
  </section>
</template>

<script>
import { locateServer, handleResponse } from "@/assets/js/utils";
import CONST from "@/assets/js/const";
import QRCode from "qrcode";
import { Decoder } from "@nuintun/qrcode";
import ClipboardJS from "clipboard";
import { Base64 } from "js-base64";
import ModalServer from "@/components/modalServer";
import ModalSubscription from "@/components/modalSubcription";
import ModalSharing from "@/components/modalSharing";
import { waitingConnected } from "@/assets/js/networkInspect";
import axios from "@/plugins/axios";
import * as dayjs from "dayjs";

export default {
  name: "Node",
  components: { ModalSubscription, ModalServer },
  filters: {
    unix2datetime(x) {
      x = dayjs.unix(x);
      let now = dayjs();
      if (localStorage["_lang"] === "zh") {
        now = now.locale("zh-cn");
      } else if (localStorage["_lang"] === "en") {
        now = now.locale("en");
      }
      return now.to(x);
    },
  },
  props: {
    outbound: {
      type: String,
      default: "proxy",
    },
    observatory: {
      type: Object,
      default() {
        return null;
      },
    },
  },
  data() {
    return {
      enterReducedSidebar: false,
      showSidebar: false,
      importWhat: "",
      showModalImport: false,
      showModalImportInBatch: false,
      currentPage: { servers: 1, subscriptions: 1 },
      tableData: {
        servers: [],
        subscriptions: [],
        connectedServer: [],
      },
      checkedRows: [],
      ready: false,
      tab: 0,
      runningState: {
        running: this.$t("common.checkRunning"),
        connectedServer: null,
        outboundToServerName: {},
      },
      showModalServer: false,
      which: null,
      modalServerReadOnly: false,
      showModalSubscription: false,
      connectedServerInTab: {
        subscriptionServer: Array(100),
        server: false,
      },
      connectedServerInfo: [],
      overHeight: false,
      clipboard: null,
    };
  },
  computed: {
    loadBalanceValid() {
      return localStorage["loadBalanceValid"] === "true";
    },
  },
  watch: {
    "runningState.running"() {
      this.updateConnectView();
    },
    outbound() {
      this.updateConnectView();
    },
    tableData(x) {
      for (const sub of x.subscriptions) {
        sub.status = dayjs(sub.status)
          .tz(dayjs.tz.guess())
          .format("YYYY-MM-DD HH:mm:ss");
      }
    },
    observatory(val) {
      for (const info of val.body.outboundStatus) {
        this.connectedServerInfo.some((x) => {
          if (
            info.which._type === x.which._type &&
            info.which.id === x.which.id &&
            info.which.sub === x.which.sub
          ) {
            for (const k in info) {
              if (k === "which" || !info.hasOwnProperty(k)) {
                continue;
              }
              x.info[k] = info[k];
            }
            return true;
          }
          return false;
        });
      }
      let minDelay = 99999;
      let index = -1;
      this.connectedServerInfo.forEach((x, i) => {
        x.selected = false;
        if (x.info.delay && x.info.delay < minDelay) {
          minDelay = x.info.delay;
          index = i;
        }
      });
      if (index >= 0) {
        this.connectedServerInfo[index].selected = true;
      }
    },
  },
  created() {
    this.$axios({
      url: apiRoot + "/touch",
    }).then((res) => {
      this.refreshTableData(res.data.data.touch, res.data.data.running);
      this.locateTabToConnected();
      this.ready = true;
    });
  },
  beforeDestroy() {
    this.clipboard.destroy();
  },
  mounted() {
    document
      .querySelector("#QRCodeImport")
      .addEventListener("change", this.handleFileChange, false);
    this.clipboard = new ClipboardJS(".sharingAddressTag");
    this.clipboard.on("success", (e) => {
      this.$buefy.toast.open({
        message: this.$t("common.success"),
        type: "is-primary",
        position: "is-top",
        queue: false,
      });
      e.clearSelection();
    });
    this.clipboard.on("error", (e) => {
      this.$buefy.toast.open({
        message: this.$t("common.fail") + ", error:" + e.toLocaleString(),
        type: "is-warning",
        position: "is-top",
        queue: false,
      });
    });
    const that = this;
    let scrollTimer = null;
    window.addEventListener("scroll", (e) => {
      clearTimeout(scrollTimer);
      setTimeout(() => {
        scrollTimer = null;
        that.overHeight = e.target.scrollingElement.scrollTop > 50;
      }, 100);
    });

    // if lastNodeTab in the local storage, set it as the current tab.
    const { lastNodeTab } = localStorage;
    if (lastNodeTab !== undefined) {
      this.tab = parseInt(lastNodeTab);
    }
  },
  methods: {
    refreshTableData(touch, running) {
      touch.servers.forEach((v) => {
        v.connected = false;
      });
      touch.subscriptions.forEach((s) => {
        s.servers.forEach((v) => {
          v.connected = false;
        });
      });
      this.tableData = touch;
      if (running !== undefined) {
        Object.assign(this.runningState, {
          running: running
            ? this.$t("common.isRunning")
            : this.$t("common.notRunning"),
          connectedServer: touch.connectedServer,
        });
      }
    },
    handleClickConnectedServer(which) {
      const that = this;
      this.locateTabToConnected(which);
      let tabIndex = -1;
      if (which._type === "server") {
        tabIndex = 1;
      } else {
        tabIndex = 2 + which.sub;
      }
      let tryCnt = 0;
      const maxTry = 5;
      const tryInterval = 500;

      function waitingAndLocate() {
        if (
          !document
            .querySelector(
              `.main-tabs > .tabs > ul > li:nth-child(${1 + tabIndex})`
            )
            .classList.contains("is-active")
        ) {
          tryCnt++;
          if (tryCnt > maxTry) {
            return;
          }
          setTimeout(waitingAndLocate, tryInterval);
          return;
        }
        console.log("ok");
        that.$nextTick(() => {
          let nodes = document.querySelectorAll(".main-tabs .b-table");
          if (which._type === "subscriptionServer") {
            // solid
            tabIndex = 2;
          }
          nodes = nodes[tabIndex].querySelectorAll("table > tbody > tr");
          const node = Array.from(nodes).find(
            (node) =>
              parseInt(
                node.querySelector('td[data-label="ID"]')?.textContent
              ) === which.id
          );
          if (!node) {
            console.warn("node not found");
            return;
          }
          node.scrollIntoView({ block: "center", inline: "center" });
          let highlightClass = "highlight-row-connected";
          if (that.runningState.running !== that.$t("common.isRunning")) {
            highlightClass = "highlight-row-disconnected";
          }
          node.classList.add(highlightClass);
          setTimeout(() => {
            node.classList.remove(highlightClass);
            setTimeout(() => {
              node.classList.add(highlightClass);
              setTimeout(() => {
                node.classList.remove(highlightClass);
              }, 200);
            }, 50);
          }, 200);
        });
      }

      waitingAndLocate();
    },
    handleClickImportInBatch() {
      this.showModalImportInBatch = true;
    },
    handleModalImportShow() {
      this.$refs.importInput.focus();
    },
    handleImportEnter(event) {
      if (event.keyCode !== 13) {
        return;
      }
      this.handleClickImportConfirm();
    },
    handleFileChange(e) {
      const that = this;
      const file = e.target.files[0];
      let elem = document.querySelector("#QRCodeImport");
      // eslint-disable-next-line no-self-assign
      elem.outerHTML = elem.outerHTML;
      this.$nextTick(() => {
        document
          .querySelector("#QRCodeImport")
          .addEventListener("change", this.handleFileChange, false);
      });
      // console.log(file);
      if (!file.type.match(/image\/.*/)) {
        this.$buefy.toast.open({
          message: this.$t("import.qrcodeError"),
          type: "is-warning",
          position: "is-top",
          queue: false,
        });
        return;
      }
      const reader = new FileReader();
      reader.onload = function (e) {
        // target.result 该属性表示目标对象的DataURL
        // console.log(e.target.result);
        const file = e.target.result;
        const qrcode = new Decoder();
        qrcode
          .scan(file)
          .then((result) => {
            console.log(result);
            that.handleClickImportConfirm(result.data);
          })
          .catch((error) => {
            console.error(error);
            that.$buefy.toast.open({
              message: that.$t("import.qrcodeError"),
              type: "is-warning",
              position: "is-top",
              queue: false,
            });
          });
      };
      reader.readAsDataURL(file);
    },
    sortNumberServers(a, b, isAsc) {
      if (!isAsc) {
        return a.servers.length < b.servers.length ? 1 : -1;
      }
      return a.servers.length > b.servers.length ? 1 : -1;
    },
    sortping(a, b, isAsc) {
      if (isNaN(parseInt(a.pingLatency))) {
        return 1;
      }
      if (isNaN(parseInt(b.pingLatency))) {
        return -1;
      }
      if (!isAsc) {
        return parseInt(a.pingLatency) < parseInt(b.pingLatency) ? 1 : -1;
      } else {
        return parseInt(a.pingLatency) > parseInt(b.pingLatency) ? 1 : -1;
      }
    },
    sortConnections(a, b, isAsc) {
      // when sorted, only connected servers on top
      // desc: error > high ping > low ping > unconnected
      // asc: low ping > high ping > error > unconnected
      if (a.connected && !b.connected) {
        return -1;
      }
      if (!a.connected && b.connected) {
        return 1;
      }
      if (!isAsc) {
        if (isNaN(parseInt(a.pingLatency))) {
          return -1;
        }
        if (isNaN(parseInt(b.pingLatency))) {
          return 1;
        }
        return parseInt(a.pingLatency) < parseInt(b.pingLatency) ? 1 : -1;
      } else {
        if (isNaN(parseInt(a.pingLatency))) {
          return 1;
        }
        if (isNaN(parseInt(b.pingLatency))) {
          return -1;
        }
        return parseInt(a.pingLatency) > parseInt(b.pingLatency) ? 1 : -1;
      }
    },
    filterConnectedServer(servers, outbound = this.outbound) {
      const connectedServers = [];
      if (servers instanceof Array) {
        for (let s of servers) {
          if (s.outbound === outbound) {
            connectedServers.push(s);
          }
        }
        return connectedServers.length ? connectedServers : null;
      }
      return servers;
    },
    updateConnectView() {
      let connectedServer = this.runningState.connectedServer;
      // associate outbounds and servers
      this.runningState.outboundToServerName = {};
      this.runningState.connectedServer?.forEach((cs) => {
        const server = locateServer(this.tableData, cs);
        if (
          this.runningState.outboundToServerName[cs.outbound] &&
          typeof this.runningState.outboundToServerName[cs.outbound] !==
            "number"
        ) {
          this.runningState.outboundToServerName[cs.outbound] = 1;
        }
        if (
          typeof this.runningState.outboundToServerName[cs.outbound] ===
          "number"
        ) {
          this.runningState.outboundToServerName[cs.outbound]++;
        } else {
          this.runningState.outboundToServerName[cs.outbound] = server.name;
        }
      });

      connectedServer = this.filterConnectedServer(connectedServer);
      // clear connected state
      this.tableData.servers.forEach((v) => {
        v.connected && (v.connected = false);
      });
      this.tableData.subscriptions.forEach((s) => {
        s.servers.forEach((v) => {
          v.connected && (v.connected = false);
        });
      });
      if (connectedServer) {
        let server = locateServer(this.tableData, connectedServer);
        if (server instanceof Array) {
          for (const s of server) {
            s.connected = true;
          }
        } else {
          server.connected = true;
          server = [server];
        }
        this.connectedServerInfo = [];
        for (const i in server) {
          let subscription_name = null;
          if (connectedServer[i]._type === "subscriptionServer") {
            subscription_name =
              this.tableData.subscriptions[
                connectedServer[i].sub
              ].host.toUpperCase();
          }
          this.connectedServerInfo.push({
            info: {
              ...server[i],
              subscription_name,
              alive: null,
              delay: null,
              outbound_tag: null,
              last_seen_time: null,
              last_error_reason: null,
              last_try_time: null,
            },
            which: connectedServer[i],
            showContent: true,
            selected: false,
          });
        }
      } else {
        this.connectedServerInfo = [];
      }

      this.connectedServerInfo.sort((x, y) => {
        return x.info.name > y.info.name;
      });

      this.connectedServerInTab.server = false;
      for (const i in this.connectedServerInTab.subscriptionServer) {
        this.connectedServerInTab.subscriptionServer[i] = false;
      }
      if (connectedServer) {
        let servers = connectedServer;
        if (!(connectedServer instanceof Array)) {
          servers = [connectedServer];
        }
        for (const s of servers) {
          if (s._type === "server") {
            this.connectedServerInTab.server = true;
          } else if (s._type === "subscriptionServer") {
            this.connectedServerInTab.subscriptionServer[s.sub] = true;
          }
        }
      }
      this.$emit("input", this.runningState);
    },
    locateTabToConnected(which) {
      let whichServer = which;
      if (!whichServer) {
        whichServer = this.runningState.connectedServer;
      }
      if (!whichServer) {
        return;
      }
      whichServer = this.filterConnectedServer(whichServer);
      if (!whichServer) {
        return;
      }
      if (whichServer instanceof Array) {
        whichServer = whichServer[0];
      }
      let sub = whichServer.sub;
      let subscriptionServersOffset = 2;
      let serversOffset = 1;
      // if (this.tableData.subscriptions.length > 0) {
      //   subscriptionServersOffset++;
      //   serversOffset++;
      // }
      // if (this.tableData.servers.length > 0) {
      //   subscriptionServersOffset++;
      // }
      if (whichServer._type === CONST.SubscriptionServerType) {
        this.tab = sub + subscriptionServersOffset;
      } else if (whichServer._type === CONST.ServerType) {
        this.tab = serversOffset;
      }
    },
    handleClickImportQRCode() {
      document.querySelector("#QRCodeImport").click();
    },
    handleClickImport() {
      this.showModalImport = true;
    },
    handleClickImportConfirm(value) {
      if (typeof value != "string") {
        value = null;
      }
      return this.$axios({
        url: apiRoot + "/import",
        method: "post",
        data: {
          url: value || this.importWhat,
        },
      }).then((res) => {
        if (res.data.code === "SUCCESS") {
          this.refreshTableData(res.data.data.touch, res.data.data.running);
          this.updateConnectView();
          this.$buefy.toast.open({
            message: this.$t("common.success"),
            type: "is-primary",
            position: "is-top",
            queue: false,
          });
          this.showModalImport = false;
          this.showModalImportInBatch = false;
          this.importWhat = "";
        } else {
          this.$buefy.toast.open({
            message: res.data.message,
            type: "is-warning",
            position: "is-top",
            queue: false,
          });
        }
      });
    },
    deleteSelectedServers() {
      this.$axios({
        url: apiRoot + "/touch",
        method: "delete",
        data: {
          touches: this.checkedRows.map((x) => {
            return {
              id: x.id,
              _type: x._type,
            };
          }),
        },
      }).then((res) => {
        if (res.data.code === "SUCCESS") {
          this.refreshTableData(res.data.data.touch, res.data.data.running);
          this.checkedRows = [];
          this.updateConnectView();
        } else {
          this.$buefy.toast.open({
            message: res.data.message,
            type: "is-warning",
            position: "is-top",
            duration: 5000,
            queue: false,
          });
        }
      });
    },
    handleClickDelete() {
      this.$buefy.dialog.confirm({
        title: this.$t("delete.title"),
        message: this.$t("delete.message"),
        confirmText: this.$t("operations.delete"),
        cancelText: this.$t("operations.cancel"),
        type: "is-danger",
        hasIcon: true,
        icon: " iconfont icon-alert",
        onConfirm: () => this.deleteSelectedServers(),
      });
    },
    handleClickAboutConnection(row, sub) {
      let cancel;
      if (!row.connected) {
        //该节点并未处于连接状态，因此进行连接
        waitingConnected(
          this.$axios({
            url: apiRoot + "/connection",
            method: "post",
            data: {
              id: row.id,
              _type: row._type,
              sub: sub,
              outbound: this.outbound,
            },
            cancelToken: new axios.CancelToken(function executor(c) {
              cancel = c;
            }),
          }).then((res) => {
            if (res.data.code === "SUCCESS") {
              Object.assign(this.runningState, {
                running: res.data.data.running
                  ? this.$t("common.isRunning")
                  : this.$t("common.notRunning"),
                connectedServer: res.data.data.touch.connectedServer,
              });
              this.$nextTick(() => {
                this.updateConnectView();
              });
            } else {
              this.$buefy.toast.open({
                message: res.data.message,
                type: "is-warning",
                position: "is-top",
                duration: 5000,
                queue: false,
              });
              this.deleteSelectedServers();
            }
          }),
          3 * 1000,
          cancel
        );
      } else {
        this.$axios({
          url: apiRoot + "/connection",
          method: "delete",
          data: {
            id: row.id,
            _type: row._type,
            sub: sub,
            outbound: this.outbound,
          },
        }).then((res) => {
          if (res.data.code === "SUCCESS") {
            row.connected = false;
            Object.assign(this.runningState, {
              running: res.data.data.running
                ? this.$t("common.isRunning")
                : this.$t("common.notRunning"),
              connectedServer: res.data.data.touch.connectedServer,
            });
            this.updateConnectView();
          } else {
            this.$buefy.toast.open({
              message: res.data.message,
              type: "is-warning",
              position: "is-top",
              duration: 5000,
              queue: false,
            });
          }
        });
      }
    },
    handleClickLatency(ping) {
      let touches = JSON.stringify(
        this.checkedRows.map((x) => {
          //穷举sub
          let sub = this.tableData.subscriptions.findIndex((subscription) =>
            subscription.servers.some((y) => x === y)
          );
          return {
            id: x.id,
            _type: x._type,
            sub: sub === -1 ? null : sub,
          };
        })
      );
      this.checkedRows.forEach((x) => (x.pingLatency = "testing...")); //refresh
      // this.checkedRows = [];
      let timerTip = setTimeout(() => {
        this.$buefy.toast.open({
          message: this.$t("latency.message"),
          type: "is-primary",
          position: "is-top",
          duration: 5000,
          queue: false,
        });
      }, 10 * 1200);
      this.$axios({
        url: apiRoot + (ping ? "/pingLatency" : "/httpLatency"),
        params: {
          whiches: touches,
        },
        timeout: 0,
      })
        .then((res) => {
          handleResponse(
            res,
            this,
            () => {
              res.data.data.whiches.forEach((x) => {
                let server = locateServer(this.tableData, x);
                server.pingLatency = x.pingLatency;
              });
              this.updateConnectView();
            },
            () => {
              this.$buefy.toast.open({
                message: res.data.message,
                type: "is-warning",
                position: "is-top",
                queue: false,
                duration: 5000,
              });
              this.checkedRows.forEach((x) => (x.pingLatency = ""));
            }
          );
        })
        .finally(() => {
          clearTimeout(timerTip);
        });
    },
    // eslint-disable-next-line no-unused-vars
    handleTabsChange(index) {
      // store the index in local storage to remember the tab.
      localStorage.lastNodeTab = index;
      this.checkedRows = [];
    },
    isCheckedRowsDeletable() {
      // CONST.SubscriptionServerType is not deletable
      return (
        this.checkedRows.length > 0 &&
        this.checkedRows.every((x) => x._type !== CONST.SubscriptionServerType)
      );
    },
    isCheckedRowsPingable() {
      // CONST.SubscriptionServerType is not deletable
      return (
        this.checkedRows.length > 0 &&
        this.checkedRows.some(
          (x) =>
            x._type === CONST.ServerType ||
            x._type === CONST.SubscriptionServerType
        )
      );
    },
    handleClickShare(row, sub) {
      const TYPE_MAP = {
        [CONST.SubscriptionServerType]: "SERVER",
        [CONST.ServerType]: "SERVER",
        [CONST.SubscriptionType]: "SUBSCRIPTION",
      };
      this.$axios({
        url: apiRoot + "/sharingAddress",
        method: "get",
        params: {
          touch: {
            id: row.id,
            _type: row._type,
            sub,
          },
        },
      }).then((res) => {
        handleResponse(res, this, () => {
          this.$buefy.modal.open({
            width: 500,
            component: ModalSharing,
            props: {
              title: TYPE_MAP[row._type],
              sharingAddress: res.data.data.sharingAddress,
              shortDesc: row.name || row.host || row.address,
              type: row._type,
            },
          });
        });
      });
    },
    handleClickUpdateSubscription(row) {
      this.$axios({
        url: apiRoot + "/subscription",
        method: "put",
        data: {
          id: row.id,
          _type: row._type,
        },
      }).then((res) => {
        handleResponse(res, this, () => {
          this.refreshTableData(res.data.data.touch, res.data.data.running);
          this.updateConnectView();
          this.$buefy.toast.open({
            message: this.$t("common.success"),
            type: "is-primary",
            position: "is-top",
            duration: 5000,
            queue: false,
          });
        });
      });
    },
    handleClickCreate() {
      this.modalServerReadOnly = false;
      this.which = null;
      this.showModalServer = true;
    },
    handleClickModifyServer(row) {
      this.modalServerReadOnly = false;
      this.which = Object.assign({}, row);
      this.which.servers = [];
      this.showModalServer = true;
    },
    handleClickViewServer(row, sub) {
      this.modalServerReadOnly = true;
      this.which = { ...row, sub };
      this.showModalServer = true;
    },
    handleModalServerSubmit(url) {
      this.$axios({
        url: apiRoot + "/import",
        method: "post",
        data: {
          url: url,
          which: this.which,
        },
        timeout: 0,
      }).then((res) => {
        handleResponse(res, this, () => {
          this.$buefy.toast.open({
            message: this.$t("common.success"),
            type: "is-primary",
            position: "is-top",
            duration: 3000,
            queue: false,
          });
          this.showModalServer = false;
          this.refreshTableData(res.data.data.touch, res.data.data.running);
          this.updateConnectView();
        });
      });
    },
    handleClickModifySubscription(row) {
      this.which = Object.assign({}, row);
      this.which.servers = [];
      this.showModalSubscription = true;
    },
    handleModalSubscriptionSubmit(subscription) {
      this.$axios({
        url: apiRoot + "/subscription",
        method: "patch",
        data: {
          subscription,
        },
      }).then((res) => {
        handleResponse(res, this, () => {
          this.$buefy.toast.open({
            message: this.$t("common.success"),
            type: "is-primary",
            position: "is-top",
            duration: 3000,
            queue: false,
          });
          this.showModalSubscription = false;
          this.refreshTableData(res.data.data.touch, res.data.data.running);
          this.updateConnectView();
        });
      });
    },
  },
};
</script>

<style lang="scss" scoped>
td {
  font-size: 0.9em;
}

.node-section {
  margin-top: 1rem;

  .iconfont {
    margin-right: 0.1em;
  }

  .operate-box {
    > * {
      margin-right: 0.5rem;
    }
  }
}

.card {
  max-width: 500px;
  margin: auto;
}

.ping-latency {
  font-size: 0.8em;
}
</style>

<style lang="scss">
@import "~bulma/sass/utilities/all";

#toolbar {
  @media screen and (max-width: 450px) {
    &.float-toolbar {
      top: 4.25rem;
      margin-left: 25px;
      width: calc(100% - 50px);
    }
    .field.is-grouped .field:not(:last-child) {
      margin-right: 0.3rem;
    }
  }

  .field.is-grouped.is-grouped-multiline:last-child {
    margin-bottom: 0;
  }

  padding: 0.75em 0.75em;
  margin-bottom: 1rem;
  position: sticky;
  top: 65px;
  z-index: 2;
  background: rgba(0, 0, 0, 0.05);
  width: 100%;
  border-radius: 3px;
  pointer-events: none;

  * {
    pointer-events: auto;
  }

  .right {
    position: absolute;
    right: 0.75rem;
    top: 0.75em;
    /*max-width: 70%;*/
  }

  transition: all 200ms linear;

  button {
    transition: all 100ms ease-in-out;
  }
}

.tabs {
  .icon + span {
    color: #ff6719; //方案1
  }

  .icon {
    display: none; //方案1
    margin: 0 0 0 -0.5em !important;

    .iconfont {
      font-size: 32px;
      color: coral;
    }
  }
}

tr.is-connected-running {
  $c: #bbdefb;
  background: $c;
  color: findColorInvert($c);
}

tr.is-connected-not-running {
  $c: rgba(255, 69, 58, 0.4);
  background: $c;
  color: findColorInvert($c);
}

@keyframes loading-rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.not-show {
  opacity: 0;
  pointer-events: none !important;
  overflow: hidden;
  width: 0 !important;
  display: inline-block !important;
  padding-left: 0 !important;
  margin-left: 0 !important;
  border-left: 0 !important;
  padding-right: 0 !important;
  margin-right: 0 !important;
  border-right: 0 !important;
}

.not-display {
  display: none;
}

table td,
table th {
  vertical-align: middle !important;
}

.dialog .mdi-.iconfont.icon-alert {
  font-size: 40px;
}

.qrcode#canvas {
  min-height: 300px !important;
  min-width: 300px !important;
}

$coverBackground: rgba(0, 0, 0, 0.6);
.tag-cover {
  height: 100%;
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  background-color: $coverBackground !important;
  transition: all 0.5s ease;
  cursor: pointer;
  text-align: center;
  line-height: 22px;
  user-select: none;
}

#tag-cover-text {
  color: findColorInvert($coverBackground);
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: calc(100% - 8px);
  display: none;
  justify-content: center;
  align-items: center;
  z-index: 1;
  font-size: 12px;
  pointer-events: none;
}

.mobile-small {
  @media screen and (max-width: 450px) {
    border-radius: 2px;
    font-size: 0.65rem;
  }
}

.b-sidebar.node-status-sidebar-reduced > .sidebar-content.is-fixed {
  z-index: 1;
  left: 1px;
  top: 4.25rem;
  background-color: white;
  width: unset;
  line-height: 0;
  border-radius: 4px;
}

.b-sidebar.node-status-sidebar > .sidebar-content.is-fixed {
  left: 1px;
  top: 4.25rem;
  background-color: white;
  max-height: calc(100vh - 5rem);
  overflow-y: auto;

  .message {
    cursor: pointer;
  }

  .tabs:not(:last-child),
  .pagination:not(:last-child),
  .message:not(:last-child),
  .level:not(:last-child),
  .breadcrumb:not(:last-child),
  .highlight:not(:last-child),
  .block:not(:last-child),
  .title:not(:last-child),
  .subtitle:not(:last-child),
  .table-container:not(:last-child),
  .table:not(:last-child),
  .progress:not(:last-child),
  .notification:not(:last-child),
  .content:not(:last-child),
  .box:not(:last-child) {
    margin-bottom: 0.25rem;
  }
}

tr.highlight-row-connected {
  transition: background-color 0.05s linear;
  background-color: #a8cff0;
}

tr.highlight-row-disconnected {
  transition: background-color 0.05s linear;
  background-color: rgba(255, 69, 58, 0.55);
}

.click-through {
  pointer-events: none;
}
.address-column {
  max-width: 350px!important;
  overflow: hidden!important;
  text-overflow: ellipsis!important;
}
.latency-column {
  max-width: 120px!important;
  overflow: hidden!important;
  text-overflow: ellipsis!important;
  white-space: nowrap;
}
.latency-valid {
  color: green;
}

@media screen and (max-width: 1920px) {
  .latency-column {
    max-width: 70px!important;
  }
  .address-column {
    max-width: 150px!important;
  }
}
</style>
